package org.truenewx.tnxjee.core.enums;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.truenewx.tnxjee.core.spec.Mutability;
import org.truenewx.tnxjee.core.util.*;

@Component
public class EnumTypeParserImpl implements EnumTypeParser {

    @Autowired
    protected EnumDictResolver enumDictResolver;
    protected final Map<Locale, Resource[]> localeResourceMapping = new HashMap<>();
    private final Set<String> parsedResources = new HashSet<>(); // 记录已解析的资源清单，以避免重复解析，导致可能的无限递归循环
    protected final Map<Resource, List<EnumType>> resourceEnumTypesMapping = new HashMap<>();

    protected Resource[] getResources(Locale locale) {
        if (locale == null) {
            locale = Locale.getDefault();
        }
        Resource[] resources = this.localeResourceMapping.get(locale);
        if (resources == null) {
            resources = IOUtil.findI18nResources(
                    ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "META-INF/enums",
                    locale, FileExtensions.XML);
            this.localeResourceMapping.put(locale, resources);
        }
        return resources;
    }

    /**
     * 从指定文件中读取内容并构建枚举类型<br>
     * 如果指定配置文件不存在，或文件中没有指定的枚举类型，则返回null
     *
     * @param locale  区域
     * @param type    枚举类型名
     * @param subType 枚举子类型名
     * @return 枚举类型
     */
    @Override
    public EnumType parse(Locale locale, String type, String subType) {
        EnumType[] enumTypes = new EnumType[1];
        Resource[] resources = getResources(locale);
        forEach(resources, locale, enumType -> {
            if (type.equals(enumType.getName()) && StringUtil.equalsIgnoreBlank(subType, enumType.getSubname())) {
                enumTypes[0] = enumType;
                return false;
            }
            return true;
        });
        if (enumTypes[0] != null) {
            return enumTypes[0];
        }
        return null;
    }

    @Override
    public void forEach(Locale locale, Consumer<EnumType> consumer) {
        Resource[] resources = getResources(locale);
        forEach(resources, locale, enumType -> {
            consumer.accept(enumType);
            return true;
        });
    }

    private void forEach(Resource[] resources, Locale locale, Predicate<EnumType> predicate) {
        for (Resource resource : resources) {
            if (resource != null && resource.exists() && this.parsedResources.add(resource.toString())) {
                List<EnumType> enumTypes = this.resourceEnumTypesMapping.get(resource);
                if (enumTypes == null) {
                    enumTypes = parse(resource, locale);
                    this.resourceEnumTypesMapping.put(resource, enumTypes);
                }
                for (EnumType enumType : enumTypes) {
                    if (!predicate.test(enumType)) {
                        return;
                    }
                }
            }
        }
    }

    protected List<EnumType> parse(Resource resource, Locale locale) {
        List<EnumType> list = new ArrayList<>();
        try (InputStream in = resource.getInputStream()) {
            SAXReader reader = new SAXReader();
            Document doc = reader.read(in);
            Element rootElement = doc.getRootElement();
            int nameSize = MathUtil.parseInt(rootElement.attributeValue("name-size"));
            List<Element> typeElements = rootElement.elements("type");
            for (Element typeElement : typeElements) {
                String typeName = typeElement.attributeValue("name");
                Assert.isTrue(!StringUtil.isNumbers(typeName),
                        () -> "The type name('" + typeName + "') cannot be all number");
                Assert.isTrue(nameSize <= 0 || typeName.length() <= nameSize,
                        () -> "The type name('" + typeName + "') length has exceeded " + nameSize);

                String typeSubname = typeElement.attributeValue("subname");
                String typeCaption = typeElement.attributeValue("caption");
                Mutability mutability = parseMutability(typeElement, Mutability.BASIC);
                EnumType enumType = new EnumType(typeName, typeSubname, typeCaption, mutability);
                addEnumItemsToEnumType(typeElement, enumType, locale);
                list.add(enumType);
            }
        } catch (DocumentException | IOException e) {
            LogUtil.error(getClass(), e);
        }
        return list;
    }

    protected static Mutability parseMutability(Element element, Mutability defaultMutability) {
        String mutabilityValue = element.attributeValue("mutability");
        if (StringUtils.isNotBlank(mutabilityValue)) {
            return Mutability.valueOf(mutabilityValue.toUpperCase());
        }
        return defaultMutability;
    }

    protected void addEnumItemsToEnumType(Element typeElement, EnumType enumType, Locale locale) {
        List<Element> subElements = typeElement.elements();
        for (Element subElement : subElements) {
            if ("item".equals(subElement.getName())) {
                // 枚举类型下已有的枚举项数量+1作为序号
                int ordinal = enumType.getItems().size() + 1;
                EnumItem item = buildEnumItem(subElement, ordinal);
                enumType.addItem(item);
            } else if ("ref".equals(subElement.getName())) {
                String refSubname = subElement.attributeValue("subname");
                if (StringUtils.isNotBlank(refSubname)) {
                    Assert.isTrue(StringUtils.isBlank(enumType.getSubname()),
                            () -> "The enum type('" + enumType.getIdentity() + "') is not main enum type, " +
                                    "only main enum type (no subname) can reference sub type.");
                    EnumType refType = this.enumDictResolver.getEnumType(enumType.getName(), refSubname, locale);
                    Assert.notNull(refType,
                            () -> "The ref enum type('" + enumType.getName() + EnumTypeIdentity.SEPARATOR + refSubname
                                    + "') does not exist");
                    Collection<EnumItem> refItems = refType.getItems();
                    for (EnumItem refItem : refItems) {
                        // 枚举类型下已有的枚举项数量+1作为序号
                        int ordinal = enumType.getItems().size() + 1;
                        EnumItem item = refItem.clone(ordinal, false);
                        item.setRefSubname(refSubname);
                        enumType.addItem(item);
                    }
                }
            }
        }
    }

    protected EnumItem buildEnumItem(Element itemElement, int ordinal) {
        String key = itemElement.attributeValue("key");
        String caption = itemElement.attributeValue("caption");
        Mutability mutability = parseMutability(itemElement, Mutability.DISABLE);
        EnumItem item = new EnumItem(ordinal, key, caption, mutability);
        String value = itemElement.attributeValue("value");
        if (StringUtils.isBlank(value)) {
            value = key;
        }
        item.setValue(value);
        addChildrenToEnumItem(itemElement, item);
        return item;
    }

    /**
     * 解析指定枚举项元素中的子枚举项加入指定枚举项中
     *
     * @param itemElement 枚举项元素
     * @param enumItem    枚举项
     */
    protected void addChildrenToEnumItem(Element itemElement, EnumItem enumItem) {
        List<Element> childElements = itemElement.elements("item");
        for (int i = 0; i < childElements.size(); i++) {
            Element childElement = childElements.get(i);
            EnumItem child = buildEnumItem(childElement, i);
            enumItem.addChild(child);
        }
    }


}
